package main;

import java.awt.BorderLayout;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Vector;

import javax.swing.*;

import sort_algorithms.*;

import datastructures.Datum;

/**
 * @author kf6kjg
 * 
 */
public class SortDemo {
  private static AbstractSorter            sorter;
  
  private static Vector<Datum>             data_presort;
  
  public static Vector<Datum>              data;
  
  private static DisplayPanel              dp;
  
  private static JLabel                    infoPanel;
  
  private static boolean                   isPaused = true;
  
  private static ArrayList<AbstractSorter> Sorters  = new ArrayList<AbstractSorter>();
  
  /**
   * @param args
   */
  public static void main(String[] args) {
    DisplayFrame frame = new DisplayFrame();
    frame.setTitle(GlobalSettings.WindowTitle);
    
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    
    frame.setLayout(new BorderLayout());
    
    SortDemo.dp = new DisplayPanel();
    frame.add("Center", SortDemo.dp);
    
    SortDemo.data_presort = SortDemo.GenerateRandom(GlobalSettings.ElementCount);
    SortDemo.data = (Vector<Datum>) data_presort.clone();
    SortDemo.dp.setData(data);
    
    SortDemo.Sorters.add(new RandomSort());
    SortDemo.Sorters.add(new BubbleSort());
    SortDemo.Sorters.add(new BubbleSortIIT());
    
    {
      JMenuItem menuEntry;
      
      int index = 0;
      
      for (AbstractSorter sorter : SortDemo.Sorters) {
        menuEntry = new JMenuItem(sorter.getName() + " (CTRL-" + (++index) + ")");
        
        // HACK: This needs to be replaced with a more robust implementation.
        menuEntry.addActionListener(new java.awt.event.ActionListener() {
          public void actionPerformed(java.awt.event.ActionEvent e) {
            AbstractSorter as = null;
            
            // HACK: Just go find the one that matches!
            for (AbstractSorter sorter : SortDemo.Sorters) {
              if (((JMenuItem) e.getSource()).getText().startsWith(sorter.getName())) {
                as = sorter;
                break;
              }
            }
            
            if (as != null) {
              SortDemo.sorter = as;
              SortDemo.sorter.reset();
            }
          }
        });
        
        frame.getAlgorithmMenu().add(menuEntry);
      }
    }
    
    SortDemo.sorter = SortDemo.Sorters.get(0);
    
    SortDemo.infoPanel = new JLabel(GlobalSettings.InfoPanelHalted + ":   " + SortDemo.sorter.getName());
    frame.add("South", SortDemo.infoPanel);
    
    JTextArea help = new JTextArea("Commands: \n" + "\n" + " Generation: \n" + "  A - Ascending \n" + "  S - Random \n" + "  D - Descending \n" + "\n" + " Sort Algorithms: \n" + "  PgUp - Prev \n" + "  PgDn - Next \n" + "\n" + " Speed: \n" + "  Up Arrow - Faster \n" + "  Dn Arrow - Slower \n" + "\n" + " Commands: \n" +
    // "  H - Show/Hide Help \n" +
        "  Spacebar - Pause \n" + "  Esc - Quit \n" + "");
    help.setEditable(false);
    help.setFocusable(false);
    // frame.add("West", help);
    
    frame.addKeyListener(new SortDemo.KeyboardHandler());
    
    frame.setVisible(true);
    
    // AbstractSorter sorter = new RandomSort(data);
    // SortDemo.sorter = new BubbleSortIIT(data);
    
    while (frame.isVisible()) {
      if (!(SortDemo.isPaused || SortDemo.sorter.isSorted())) {
        SortDemo.infoPanel.setText(GlobalSettings.InfoPanelRunning + ":   " + SortDemo.sorter.getName() + "         Delay: " + GlobalSettings.FrameDelay + "ms");
        
        SortDemo.sorter.step();
        
        dp.repaint();
      }
      else if (SortDemo.isPaused) {
        SortDemo.infoPanel.setText(GlobalSettings.InfoPanelHalted + ":   " + SortDemo.sorter.getName() + "         Delay: " + GlobalSettings.FrameDelay + "ms");
      }
      else {
        SortDemo.infoPanel.setText(GlobalSettings.InfoPanelComplete + ":   " + SortDemo.sorter.getName() + "         Delay: " + GlobalSettings.FrameDelay + "ms");
      }
      
      try {
        Thread.sleep(GlobalSettings.FrameDelay);
      }
      catch (InterruptedException e) {
      }
    }
  }
  
  // I believe that the other two generators could be combined into this one... kf6kjg
  private static Vector<Datum> GenerateRandom(int elements) {
    Vector<Datum> data = new Vector<Datum>(elements);
    
    while (--elements >= 0) {
      data.add(new Datum((int) (Math.random() * GlobalSettings.VertDetail)));
    }
    
    return data;
  }
  
  private static Vector<Datum> GenerateAscending(int elements, int noiseMagnitude) {
    Vector<Datum> data = new Vector<Datum>(elements);
    
    int step = GlobalSettings.VertDetail / (elements + 1);
    int length = step;
    int value;
    while (--elements >= 0) {
      value = length - (int) (Math.random() * noiseMagnitude) + noiseMagnitude / 2;
      if (value > GlobalSettings.VertDetail) value = GlobalSettings.VertDetail;
      data.add(new Datum(value));
      length += step;
    }
    
    return data;
  }
  
  private static Vector<Datum> GenerateDescending(int elements, int noiseMagnitude) {
    Vector<Datum> data = new Vector<Datum>(elements);
    
    int step = GlobalSettings.VertDetail / (elements + 1);
    int length = GlobalSettings.VertDetail - step;
    int value;
    while (--elements >= 0) {
      value = length - (int) (Math.random() * noiseMagnitude) + noiseMagnitude / 2;
      if (value > GlobalSettings.VertDetail) value = GlobalSettings.VertDetail;
      data.add(new Datum(value));
      length -= step;
    }
    
    return data;
  }
  
  /**
   * Sets whether or not the updating process is continuing.
   * 
   * @param state
   */
  public static boolean getPauseStatus() {
    return SortDemo.isPaused;
  }
  
  /**
   * Sets whether or not the updating process is continuing.
   * 
   * @param state
   */
  public static void setPauseStatus(boolean state) {
    SortDemo.isPaused = state;
  }
  
  public static void generateRandom() {
    boolean pauseStatus = SortDemo.getPauseStatus(); // Store the pause status
    SortDemo.setPauseStatus(true); // Halt the updates and redrawing until the new data is ready.
    SortDemo.data_presort = SortDemo.GenerateRandom(GlobalSettings.ElementCount);
    SortDemo.data = (Vector<Datum>) data_presort.clone();
    SortDemo.sorter.reset();
    SortDemo.dp.setData(data);
    SortDemo.dp.repaint();
    SortDemo.setPauseStatus(pauseStatus); // Restore pause setting
  }
  
  public static void generateAscending(int noiseMagnitude) {
    boolean pauseStatus = SortDemo.getPauseStatus(); // Store the pause status
    SortDemo.setPauseStatus(true); // Halt the updates and redrawing until the new data is ready.
    SortDemo.data_presort = SortDemo.GenerateAscending(GlobalSettings.ElementCount, noiseMagnitude);
    SortDemo.data = (Vector<Datum>) data_presort.clone();
    SortDemo.sorter.reset();
    SortDemo.dp.setData(data);
    SortDemo.dp.repaint();
    SortDemo.setPauseStatus(pauseStatus); // Restore pause setting
  }
  
  public static void generateDescending(int noiseMagnitude) {
    boolean pauseStatus = SortDemo.getPauseStatus(); // Store the pause status
    SortDemo.setPauseStatus(true); // Halt the updates and redrawing until the new data is ready.
    SortDemo.data_presort = SortDemo.GenerateDescending(GlobalSettings.ElementCount, noiseMagnitude);
    SortDemo.data = (Vector<Datum>) data_presort.clone();
    SortDemo.sorter.reset();
    SortDemo.dp.setData(data);
    SortDemo.dp.repaint();
    SortDemo.setPauseStatus(pauseStatus); // Restore pause setting
  }
  
  public static boolean setElementCount(int count) {
    if (count > GlobalSettings.ElementCountMax) return false;
    if (count < GlobalSettings.ElementCountMin) return false;
    
    SortDemo.isPaused = true;
    
    GlobalSettings.ElementCount = count;
    
    SortDemo.generateRandom();
    
    SortDemo.sorter.reset();
    
    return true;
  }
  
  public static int getElementCount() {
    return GlobalSettings.ElementCount;
  }
  
  private static class KeyboardHandler implements KeyListener {
    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyPressed(java.awt.event.KeyEvent)
     */
    @Override
    public void keyPressed(KeyEvent e) {
      // Nothing
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyReleased(java.awt.event.KeyEvent)
     */
    @Override
    public void keyReleased(KeyEvent e) {
      int index;
      switch (e.getKeyCode()) {
        case KeyEvent.VK_PAGE_UP:
          index = SortDemo.Sorters.indexOf(SortDemo.sorter) + 1;
          if (index >= SortDemo.Sorters.size()) index = 0;
          SortDemo.sorter = SortDemo.Sorters.get(index);
          SortDemo.sorter.reset();
        break;
        case KeyEvent.VK_PAGE_DOWN:
          index = SortDemo.Sorters.indexOf(SortDemo.sorter) - 1;
          if (index < 0) index = SortDemo.Sorters.size() - 1;
          SortDemo.sorter = SortDemo.Sorters.get(index);
          SortDemo.sorter.reset();
        break;
        case KeyEvent.VK_SPACE:
          // TODO: Disabled due to lack of synchronization between global variable and UI check box.
          // SortDemo.setPauseStatus(!SortDemo.isPaused);
        break;
        case KeyEvent.VK_A: // Ascending
          SortDemo.generateAscending(2000);
        break;
        case KeyEvent.VK_S: // Random (Shuffled)
          SortDemo.generateRandom();
        break;
        case KeyEvent.VK_D: // Descending
          SortDemo.generateDescending(2000);
        break;
        case KeyEvent.VK_ESCAPE:
          System.exit(0);
        break;
      }
      
      int onmask = KeyEvent.CTRL_DOWN_MASK;
      int offmask = 0;
      // Handle the CTRL-? keys
      if ((e.getModifiersEx() & (onmask | offmask)) == onmask) {
        // Process the number keys
        if (e.getKeyCode() >= KeyEvent.VK_0 && e.getKeyCode() <= KeyEvent.VK_9) {
          index = e.getKeyCode() - KeyEvent.VK_1;
          
          if (index >= SortDemo.Sorters.size())
            index = 0;
          else if (index < 0) index = 0;
          
          SortDemo.sorter = SortDemo.Sorters.get(index);
          SortDemo.sorter.reset();
        }
        
        switch (e.getKeyCode()) {
          case KeyEvent.VK_EQUALS:
          case KeyEvent.VK_PLUS:
            if (GlobalSettings.FrameDelay - GlobalSettings.FrameDelayIncr > GlobalSettings.FrameDelayIncr) {
              GlobalSettings.FrameDelay -= GlobalSettings.FrameDelayIncr;
            }
          break;
          case KeyEvent.VK_MINUS:
            GlobalSettings.FrameDelay += GlobalSettings.FrameDelayIncr;
          break;
        }
      }
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see java.awt.event.KeyListener#keyTyped(java.awt.event.KeyEvent)
     */
    @Override
    public void keyTyped(KeyEvent e) {
      // Nothing
    }
    
  }
}
